package com.fqgj.sentry.policy.engine.resolver;

import com.alibaba.fastjson.JSONObject;
import com.fqgj.log.factory.LogFactory;
import com.fqgj.log.interfaces.Log;
import com.fqgj.sentry.common.exception.BizException;
import com.fqgj.sentry.policy.engine.domain.*;
import com.fqgj.sentry.policy.engine.domain.enums.*;
import com.fqgj.sentry.policy.engine.exception.ParserException;
import com.fqgj.sentry.policy.engine.exception.ParserExceptionEnum;
import com.fqgj.sentry.policy.engine.utils.Utils;
import org.apache.commons.lang.StringUtils;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by kennykong on 2017/7/14.
 */
public class Parser {

    public static final Log LOGGER = LogFactory.getLog(Parser.class);

    private VariableMap varMap;

    private Variable setVar(Variable var) {
        return varMap.setVariable(var.getName(), var);
    }

    /**
     * 默认决策
     */
    private Decision defaultDecision;

    public Decision getDefaultDecision() {
        return defaultDecision;
    }

    public Parser setDefaultDecision(Decision defaultDecision) {
        this.defaultDecision = defaultDecision;
        return this;
    }

    /**
     * 处理策略集
     *
     * @param policySet
     * @return3
     */
    public PolicySet processPolicySet(PolicySet policySet) throws BizException {
        try {
            this.varMap = policySet.getInputVarMap();

            PolicyComposeEnum policyCompose = policySet.getPolicyCompose();
            //如没有组合类型就设置默认为FIRST
            if (policyCompose == null) {
                policyCompose = PolicyComposeEnum.FIRST;
            }

            List<Policy> policySetList = policySet.getPolicySet();

            Comparator<Policy> sortComp = (x, y) -> {
                int order1 = (x.getOrder() == null ? 0 : x.getOrder());
                int order2 = (y.getOrder() == null ? 0 : y.getOrder());
                return Integer.compare(order1, order2);
            };

            Predicate<Policy> commonRulePd = p -> {
                Rule commonRule = p.getCommonRule();
                boolean isSelected = true;
                if (commonRule != null) {
                    isSelected = processRule(commonRule).getRuleResult().getHit();
                    if (!isSelected) {
                        LOGGER.info("策略id:{},策略名称：{},前置规则 返回false", p.getPolicyId(), p.getPolicyName());
                    }
                }
                return isSelected;
            };

            Stream<Policy> policyStream = policySetList.stream().sorted(sortComp);
            // 保存成List多次调用，Stream只能调用一次
            List<Policy> sortedPolicyList = policyStream.collect(Collectors.toList());

            //比较器: Policy产生的Decision的优先级
            Comparator<Policy> comp = (p1, p2) -> {
                Integer first = null;
                try {
                    first = Utils.getDecisionPriority(p1.getPolicyResult().getPolicyDecision());
                } catch (ParserException e) {
                    e.setErrorObj(first);
                    String errorObj = e.getErrorObj();
                    String msg = "getDecisionPriority error, first policy content: " + errorObj;
                    LOGGER.error(msg, e);
                    throw new BizException(ParserExceptionEnum.get_decision_priority_error, msg);
                }
                Integer second = null;
                try {
                    second = Utils.getDecisionPriority(p2.getPolicyResult().getPolicyDecision());
                } catch (ParserException e) {
                    e.setErrorObj(second);
                    String errorObj = e.getErrorObj();
                    String msg = "getDecisionPriority error, second policy content: " + errorObj;
                    LOGGER.error(msg, e);
                    LOGGER.error("getDecisionPriority: {}", e);
                    throw new BizException(ParserExceptionEnum.get_decision_priority_error, msg);
                }
                // 按逆序排列
                return Integer.compare(second, first);
            };
//            Stream<Policy> resultPlcStream = null;
            Optional<Policy> optPolicy = Optional.empty();
            if (policyCompose.equals(PolicyComposeEnum.WORST)) {
                //得分处理
                Stream<Policy> pScoreStream = sortedPolicyList.stream()
                        .filter(p -> p.getPolicyType().equals(PolicyTypeEnum.SCORE));
                // 调用collect强制惰性表达式执行
                List<Policy> resultScoreList = pScoreStream
                        //过滤前置规则
                        .filter(commonRulePd)
                        .map(p -> processPolicy(p))
                        .collect(Collectors.toList());
                //规则处理
                Stream<Policy> pDecideStream = sortedPolicyList.stream()
                        .filter(p -> p.getPolicyType().equals(PolicyTypeEnum.DECIDE));
                //取优先级最高的
                optPolicy = pDecideStream
                        //过滤前置规则
                        .filter(commonRulePd)
                        .map(p -> processPolicy(p))
                        .parallel()
                        .sorted(comp)
                        .findFirst();
            }
            if (policyCompose.equals(PolicyComposeEnum.FIRST)) {
                //保存处理过的元素
                List<Policy> processedPolicy = new ArrayList<>();
                //判断Policy的结果的优先级是否是最高的优先级
                Predicate<Policy> isHighestPd = p -> {
                    Decision policyDecision = p.getPolicyResult().getPolicyDecision();
                    Boolean isFiltered = false;
                    // 没有decision的，decision优先级不是最高的统统滤掉，剩下的就是优先级最高的
                    if (policyDecision != null) {
                        isFiltered = policyDecision.getPriority().equals(p.getHighPriorityDecision().getPriority());
                    }
                    return isFiltered;
                };
                optPolicy = sortedPolicyList.stream()
                        //只需要处理决策型的策略
                        .filter(p -> p.getPolicyType().equals(PolicyTypeEnum.DECIDE))
                        //过滤前置规则
                        .filter(commonRulePd)
                        .map(p -> processPolicy(p))
                        //把处理过的Policy先存起来
                        .peek(processedPolicy::add)
                        .filter(isHighestPd)
                        //找到就提前结束
                        .findFirst();
                //最高优先级结果不存在的话，取处理过的列表中结果优先级最高的
                if (!optPolicy.isPresent()) {
                    optPolicy = processedPolicy.stream().sorted(comp).findFirst();
                }
            }

            Policy plc = optPolicy.orElse(null);
            PolicyResult policyResult = null;
            if (plc != null) {
                policyResult = plc.getPolicyResult();
            } else {
                policyResult = new PolicyResult();
                policyResult.setPolicyDecision(getDefaultDecision());
            }

            Decision policySetDecision = policyResult.getPolicyDecision();

            PolicySetResult policySetResult = policySet.getPolicySetResult();
            if (policySetResult == null) {
                policySetResult = new PolicySetResult();
            }
            policySetResult.setDecision(policySetDecision);
            policySet.setPolicySetResult(policySetResult);
        } catch (Exception e) {
            LOGGER.error("规则引擎解析层异常，异常信息为：", e);
            throw e;
        }


        return policySet;
    }

    /**
     * 处理策略
     *
     * @param policy
     * @return
     */

    private Policy processPolicy(Policy policy) throws BizException {

        PolicyTypeEnum type = policy.getPolicyType();
        RuleComposeEnum ruleCompose = policy.getRuleCompose();
        List<Rule> policyRules = policy.getRuleList();
        if (policyRules == null || policyRules.size() == 0) {
            return policy;
        }
        List<Rule> ruleList = policyRules.stream().sorted((x, y) -> {
            int order1 = (x.getOrder() == null ? 0 : x.getOrder());
            int order2 = (y.getOrder() == null ? 0 : y.getOrder());
            return Integer.compare(order1, order2);
        })
                .collect(Collectors.toList());

        PolicyResult policyResult = policy.getPolicyResult();
        if (policyResult == null) {
            policyResult = new PolicyResult();
        }
        policy.setPolicyResult(policyResult);
        /**
         * 评分卡型
         */
        if (type.equals(PolicyTypeEnum.SCORE)) {
            Stream<Rule> streamScoreRule = ruleList.stream().map(r -> processRuleScore(r));
            // 计算加总得分
            Double sumScore = streamScoreRule.mapToDouble(r1 -> r1.getRuleResult().getScore()).sum();
            Variable var = policy.getInitalScoreVariable();
            Double baseScore = (Double) var.getValue();
            sumScore = baseScore + sumScore;
            sumScore = Utils.splitAndRound(sumScore, 2);

            //设回变量集合
            Variable var1 = new Variable();
            var1.setName(var.getName());
            var1.setValue(sumScore);
            var1.setVarType(VariableTypeEnum.DOUBLE);
            setVar(var1);
            // 策略总分
            policyResult.setSumScore(sumScore);
        }
        /**
         * 决策型
         */
        if (type.equals(PolicyTypeEnum.DECIDE)) {

            Predicate<Rule> pdHit = r -> r.getRuleResult().getHit();

            //lazy流，只能用一次,多次使用会失败, 但此处不能处理成List，为了享受提前结束的好处, 避免不必要的调用
            Stream<Rule> streamRule = ruleList.stream().map(r -> processRule(r)).filter(pdHit);

            //按结果优先级比较器
            Comparator<Rule> compDecPriority = (r1, r2) -> {
                Integer first = null;
                try {
                    first = Utils.getDecisionPriority(r1.getRuleResult().getDecision());
                } catch (ParserException e) {
                    e.setErrorObj(first);
                    String errorObj = e.getErrorObj();
                    String msg = "getDecisionPriority error, first Rule content: " + errorObj;
                    LOGGER.error(msg, e);
                    LOGGER.error("getDecisionPriority : {}", e);
                    throw new BizException(ParserExceptionEnum.get_decision_priority_error, msg);
                }
                Integer second = null;
                try {
                    second = Utils.getDecisionPriority(r2.getRuleResult().getDecision());
                } catch (ParserException e) {
                    e.setErrorObj(second);
                    String errorObj = e.getErrorObj();
                    String msg = "getDecisionPriority error, second Rule content: " + errorObj;
                    LOGGER.error(msg, e);
                    LOGGER.error("getDecisionPriority:{}", e);
                    throw new BizException(ParserExceptionEnum.get_decision_priority_error, msg);
                }
                //按逆序排列
                return Integer.compare(second, first);
            };

            if (ruleCompose.equals(RuleComposeEnum.FIRST) || ruleCompose.equals(RuleComposeEnum.WORST)) {

                //首次和最坏匹配的Rule
                Optional<Rule> optionalRule = Optional.empty();
                /**
                 *  首次匹配
                 */
                if (ruleCompose.equals(RuleComposeEnum.FIRST)) {
                    // 这个Policy中Decision优先级最高的Decision
                    Decision highestDecision = ruleList.stream()
                            .sorted((r1, r2) -> r2.getHitDecisionOption().getPriority() - r1.getHitDecisionOption().getPriority())
                            .findFirst().get().getHitDecisionOption();
                    Predicate<Rule> pdHighPriorityDecision = r -> r.getRuleResult().getDecision().getPriority().equals(highestDecision.getPriority());

                    List<Rule> processedRule = new ArrayList<>();
                    optionalRule = streamRule.peek(processedRule::add).filter(pdHighPriorityDecision).findFirst();

                    if (!optionalRule.isPresent()) {
                        optionalRule = processedRule.stream().sorted(compDecPriority).findFirst();
                    }
                }

                /**
                 *  最坏匹配
                 */
                if (ruleCompose.equals(RuleComposeEnum.WORST)) {
                    //优先级最高的Rule结果作为整个Policy结果
                    optionalRule = streamRule.sorted(compDecPriority).findFirst();
                }

                Rule resultRule = optionalRule.orElse(null);
                if (resultRule != null) {
                    policyResult.setPolicyDecision(resultRule.getHitDecisionOption());
                    policyResult.setHit(true);
                } else {
                    policyResult.setPolicyDecision(getDefaultDecision());
                    policyResult.setHit(false);
                }
                return policy;
            }


            /**
             * 权重匹配
             */
            if (ruleCompose.equals(RuleComposeEnum.WEIGHT)) {
                List<Rule> processedRuleList = new ArrayList<>();
                Double sumScore = streamRule.peek(wr -> processedRuleList.add(wr)).mapToDouble(r1 -> {
                    Weight w = r1.getWeight();
                    Double weightScore;
                    try {
                        weightScore = Utils.computeWeightScore(w, varMap);
                    } catch (ParserException e) {
                        e.setErrorObj(w);
                        String errorObj = e.getErrorObj();
                        String msg = "computeWeightScore error, weight content: " + errorObj;
                        LOGGER.error(msg, e);
                        LOGGER.error("computeWeightScore:{}", e);
                        throw new BizException(ParserExceptionEnum.compute_weight_score_error, msg);
                    }
                    r1.getRuleResult().setWeightScore(weightScore);
                    return weightScore;
                }).sum();

                if (processedRuleList.size() > 0) {
                    policyResult.setHit(true);
                } else {
                    policyResult.setHit(false);
                }

                policyResult.setSumWeightScore(sumScore);

                // 根据sum计算决策结果
                Decision decision;
                List<WeightRange> wr = policy.getWeightRange();
                try {
                    decision = Utils.computeWeightResult(wr, sumScore);
                } catch (ParserException e) {
                    e.setErrorObj(wr);
                    String errorObj = e.getErrorObj();
                    String msg = "computeWeightResult error, WeightRangeList content: " + errorObj;
                    LOGGER.error(msg, e);
                    LOGGER.error("computeWeightResult:{}", e);
                    throw new BizException(ParserExceptionEnum.compute_weight_result_error, msg);
                }
                if (decision == null) {
                    decision = getDefaultDecision();
                }
                policyResult.setPolicyDecision(decision);

                return policy;
            }

        }
        return policy;
    }

    /**
     * 处理规则
     *
     * @param rule
     * @return
     */

    private Rule processRule(Rule rule) throws BizException {
        LOGGER.info("解析规则：{}",  JSONObject.toJSONString(rule));
        RuleTypeEnum ruleType = rule.getRuleType();
        if (ruleType.equals(RuleTypeEnum.CUSTOM)) {
            //TODO
        }
        if (ruleType.equals(RuleTypeEnum.COMMON_RULE)) {
            //TODO
        }

        ConditionGroupComposeEnum conditionGroupCompose = rule.getConditionGroupCompose();

        List<ConditionGroup> cgs = rule.getConditionGroups();

        Stream<ConditionGroup> streamConditionGroup = cgs.stream().map(cg -> processConditionGroup(cg));
        Predicate<ConditionGroup> pd = cg -> cg.getConditionGroupResult().getHit();
//        streamConditionGroup.filter(pd);
        boolean isRuleHit = false;
        if (conditionGroupCompose.equals(ConditionGroupComposeEnum.ALL)) {
            isRuleHit = streamConditionGroup.allMatch(pd);
        }
        if (conditionGroupCompose.equals(ConditionGroupComposeEnum.ANY)) {
            isRuleHit = streamConditionGroup.anyMatch(pd);
        }
        if (conditionGroupCompose.equals(ConditionGroupComposeEnum.ANY_NOT)) {
            isRuleHit = streamConditionGroup.anyMatch(pd.negate());
        }
        if (conditionGroupCompose.equals(ConditionGroupComposeEnum.ALL_NOT)) {
            isRuleHit = streamConditionGroup.allMatch(pd.negate());
        }

        RuleResult ruleResult = rule.getRuleResult();
        if (ruleResult == null) {
            ruleResult = new RuleResult();
        }
        ruleResult.setHit(isRuleHit);


        //决策结果
        if (isRuleHit) {
            ruleResult.setDecision(rule.getHitDecisionOption());
        }
        rule.setRuleResult(ruleResult);

        return rule;
    }

    /**
     * 评分卡处理
     *
     * @param rule
     * @return
     */
    private Rule processRuleScore(Rule rule) throws BizException {
        List<ConditionGroup> conditionGroupList = rule.getConditionGroups();
        Stream<ConditionGroup> streamConditionGroup = conditionGroupList.stream().map(cg -> processConditionGroup(cg));
        Predicate<ConditionGroup> pd = cg -> cg.getConditionGroupResult().getHit();
        List<ConditionGroup> hitedConditionGroup = new ArrayList<>();
        Stream<ConditionGroup> streamConditionGroupResult = streamConditionGroup.filter(pd).peek(cg -> hitedConditionGroup.add(cg));

        //理论上，一个rule只会有一个ConditionGroup分支会被匹配上，不过多于一个被匹配上也是可以的。
        Double sumScore = streamConditionGroupResult.mapToDouble(r1 -> r1.getConditionGroupResult().getHitScore()).sum();

        RuleResult ruleResult = rule.getRuleResult();
        if (ruleResult == null) {
            ruleResult = new RuleResult();
        }
        if (hitedConditionGroup.size() > 0) {
            ruleResult.setHit(true);
        } else {
            ruleResult.setHit(false);
        }
        ruleResult.setScore(sumScore);
        rule.setRuleResult(ruleResult);
        return rule;
    }

    /**
     * 条件组处理
     *
     * @param conditionGroup
     * @return
     */
    private ConditionGroup processConditionGroup(ConditionGroup conditionGroup) throws BizException {
        List<Condition> conds = conditionGroup.getConditions();
        ConditionComposeEnum conditionCompose = conditionGroup.getConditionCompose();
        if (conditionCompose == null) {
            conditionCompose = ConditionComposeEnum.AND;
        }
        Stream<Condition> streamCondition = conds.stream().map(c -> processCondition(c));
        Predicate<Condition> pd = c -> c.getHit();
        boolean isHit = false;
        Double score = 0d;
        if (conditionCompose.equals(ConditionComposeEnum.AND)) {
            isHit = streamCondition.allMatch(pd);
        }
        if (conditionCompose.equals(ConditionComposeEnum.OR)) {
            isHit = streamCondition.anyMatch(pd);
        }
        ConditionGroupResult conditionGroupResult = conditionGroup.getConditionGroupResult();
        if (conditionGroupResult == null) {
            conditionGroupResult = new ConditionGroupResult();
        }
        conditionGroupResult.setHit(isHit);

        ScoreCalculator scoreCalculator = conditionGroup.getScoreCalculator();
        //区分是否是评分卡
        if ((scoreCalculator != null) && isHit) {
            try {
                score = Utils.computeScore(scoreCalculator, varMap);
            } catch (ParserException e) {
                e.setErrorObj(scoreCalculator);
                String errorObj = e.getErrorObj();
                String msg = "computeScore error, ScoreCalculator content: " + errorObj;
                LOGGER.error(msg, e);
                LOGGER.error("computeScore:{}", e);
                throw new BizException(ParserExceptionEnum.compute_weight_result_error, msg);
            }
            conditionGroupResult.setHitScore(score);
        }

        conditionGroup.setConditionGroupResult(conditionGroupResult);
        return conditionGroup;
    }

    /**
     * 条件处理
     *
     * @param condition
     * @return
     */
    private Condition processCondition(Condition condition) throws BizException {
        String func = condition.getOperation();
        VariableOrConstant secondVariable = condition.getSecondVariable();
        //取得变量值
        Variable firstVar = condition.getFirstVariable();
        Object firstVarValue;
        try {
            firstVarValue = Utils.getVarValue(varMap, firstVar);
            firstVar.setValue(firstVarValue);
        } catch (ParserException e) {
            e.setErrorObj(firstVar);
            String errorObj = e.getErrorObj();
            String msg = "getVarValue error, firstVar content: " + errorObj;
            LOGGER.error(msg, e);
            LOGGER.error("getVarValue:{}", e);
            throw new BizException(ParserExceptionEnum.variable_not_found, msg);
        }

        List paramList = new ArrayList();

        if (secondVariable == null) {
            secondVariable = new VariableOrConstant();
            secondVariable.setIsVar(false);
            secondVariable.setConstant(null);
        }

        if (secondVariable.getIsVar()) {
            Variable varExample = secondVariable.getVariable();
            Object value = null;
            try {
                value = Utils.getVarValue(varMap, varExample);
                secondVariable.getVariable().setValue(value);
            } catch (ParserException e) {
                e.setErrorObj(varExample);
                String errorObj = e.getErrorObj();
                String msg = "getVarValue error, varExample content: " + errorObj;
                LOGGER.error(msg, e);
                throw new BizException(ParserExceptionEnum.variable_not_found, msg);
            }
            VariableTypeEnum targetType = varExample.getVarType();
            Object targetValue = Utils.paramTypeConvert(value, targetType);
            paramList.add(targetValue);

        } else {
            Object cons = secondVariable.getConstant();
            if (cons != null && StringUtils.isEmpty(cons.toString())) {
                cons = null;
            }
            //常数类型转化成第一个变量的类型，否则有可能出错
            VariableTypeEnum constType = firstVar.getVarType();
            Object consValue = Utils.paramTypeConvert(cons, constType);
            paramList.add(consValue);
        }

        Object value;
        try {
            value = Utils.groovyCompute(func, firstVarValue, paramList.toArray());
        } catch (ParserException e) {
            e.setErrorObj(condition);
            String errorObj = e.getErrorObj();
            String msg = "groovyCompute error, condition content: " + errorObj;
            LOGGER.error(msg, e);
            LOGGER.error("groovyCompute:{}", e);
            throw new BizException(ParserExceptionEnum.groovy_compute_error, msg);
        }
        condition.setHit((Boolean) value);
        return condition;
    }


}
